﻿using System;
using System.Collections.Generic;
using System.Text;

namespace BigNumber
{
  public class BigNumberMath
  {
    public static BigNumberType Abs<BigNumberType>(BigNumberType number)
      where BigNumberType : IBigNumber
    {
      BigNumberType result = (BigNumberType)number.IClone();
      if (result.IsNegative)
        result.Negate();
      return result;
    }

    public static BigNumberType GCD<BigNumberType>(BigNumberType firstNumber, BigNumberType secondNumber)
      where BigNumberType : IBigNumber
    {
      BigNumberType b = (BigNumberType)secondNumber.IClone();
      BigNumberType a = (BigNumberType)firstNumber.IClone();
      while (!b.IsZero)
      {
        BigNumberType temp = b;
        b = (BigNumberType)a.IMod(b);
        a = temp;
      }
      return a;
    }

    public static BigNumberType Power<BigNumberType>(BigNumberType baseNumber, int exponent)
      where BigNumberType : IBigNumber
    {

      if (exponent < 0)
        return (BigNumberType)baseNumber.IConstruct("0");
      BigNumberType result = (BigNumberType)baseNumber.IConstruct("1");
      BigNumberType tempBase = (BigNumberType)baseNumber.IClone();
      if (exponent == 0)
        return result;
      if (exponent == 1)
        return tempBase;
      while (exponent > 0)
      {
        int dig = exponent & 1;
        if (dig == 1)
        {
          result = (BigNumberType)result.IMul(tempBase);
          exponent--;
        }
        else
        {
          tempBase = (BigNumberType)tempBase.IMul(tempBase);
          exponent = exponent >> 1;
        }
      }
      return result;
    }

    public static BigNumberType SquareRoot<BigNumberType>(BigNumberType number)
      where BigNumberType : IBigNumber
    {
      if (number.IsNegative)
        throw new Exception("Squere root of negative numbers is not supported");
      if (number.IsZero)
        return (BigNumberType)number.IConstruct("0");
      if (number.IsOne)
        return (BigNumberType)number.IConstruct("1");

      BigNumberType two = (BigNumberType)number.IConstruct("2");
      BigNumberType temp = (BigNumberType)number.IClone();
      BigNumberType next = (BigNumberType)(temp.IAdd(number.IDiv(temp))).IDiv(two);
      while (!temp.IEqual(next))
      {
        if (temp.ISmaller(next))
          return (BigNumberType)temp;
        temp = next;
        next = (BigNumberType)(temp.IAdd(number.IDiv(temp))).IDiv(two);
      }
      return temp;
    }

    public static BigNumberType HCF<BigNumberType>(BigNumberType firstNumber, BigNumberType secondNumber)
      where BigNumberType : IBigNumber
    {
      BigNumberType l;
      BigNumberType n = firstNumber;
      BigNumberType m = secondNumber;
      do
      {
        l = (BigNumberType)n.IMod(m);
        n = m;
        m = l;
      }
      while (!m.IsZero);
      return n;
    }
    

    /// <summary>
    /// Calculates the factorial of the given number
    /// </summary>
    /// <param name="number">The number to calculate</param>
    /// <param name="resultType">A variable that determins the return type</param>
    /// <returns></returns>
    public static BigNumberType Factorial<BigNumberType>(int number)
      where BigNumberType : IBigNumber,new()
    {
      BigNumberType t = new BigNumberType();
      BigNumberType result = (BigNumberType)t.IConstruct("1");
      BigNumberType temp;
      while (number > 1)
      {
        temp = (BigNumberType)t.IConstruct(number.ToString());
        result = (BigNumberType)result.IMul(temp);
        number--;
      }
      return result;
    }

  }
}
